#include "Graphics/sdlgpu.h"

using namespace Jin;

struct Vertex {
	Vec2 pos;
	Vec3 color;
};

struct AppContext {

	std::unique_ptr<SDLWindow> window;
	std::unique_ptr<GPUDevice> gpuDevice;
	std::unique_ptr<GPUBuffer> vertexBuffer;
	std::unique_ptr<GPUBuffer> indexBuffer;

	AppContext() {
		static SDLFramework sdlfx;
		printf("SDL version: %d.%d.%d\n", sdlfx.version.major, sdlfx.version.minor, sdlfx.version.micor);
	}

	bool InitGPUDevice() {
		gpuDevice = std::make_unique<GPUDevice>(*window);
		if (!*gpuDevice) {
			printf("Create GPU Device failed!\n");
			return false;
		}
		printf("GPU Device Driver: %s\n", gpuDevice->GetDeviceDriver());

		gpuDevice->SetSwapchainParameters(SDL_GPU_SWAPCHAINCOMPOSITION_SDR, SDL_GPU_PRESENTMODE_IMMEDIATE);

		SDLFileStorage storage("shaders");
		if (!storage) {
			printf("open shaders storage failed! %s\n", SDL_GetError());
			return false;
		}

		Uint64 fileSize = 0;
		if (!storage.GetFileSize("shader2.vert.spv", fileSize)) {
			printf("get shader2.vert.spv size failed! %s\n", SDL_GetError());
			return false;
		}
		std::vector<Uint8> vertShaderCode(fileSize);
		if (!storage.ReadFile("shader2.vert.spv", vertShaderCode.data(), fileSize)) {
			printf("read shader2.vert.spv failed! %s\n", SDL_GetError());
			return false;
		}
		if (!storage.GetFileSize("shader2.frag.spv", fileSize)) {
			printf("get shader2.frag.spv size failed! %s\n", SDL_GetError());
			return false;
		}
		std::vector<Uint8> fragShaderCode(fileSize);
		if (!storage.ReadFile("shader2.frag.spv", fragShaderCode.data(), fileSize)) {
			printf("read shader2.frag.spv failed! %s\n", SDL_GetError());
			return false;
		}
		gpuDevice->onSetVertexShaderCreateInfo = [&vertShaderCode](GPUShaderCreateInfo& vertexShaderInfo, stringv driver) {
			vertexShaderInfo.code = vertShaderCode.data();
			vertexShaderInfo.code_size = vertShaderCode.size();
			vertexShaderInfo.num_uniform_buffers = 0;
			};
		gpuDevice->onSetFragmentShaderCreateInfo = [&fragShaderCode](GPUShaderCreateInfo& fragmentShaderInfo, stringv driver) {
			fragmentShaderInfo.code = fragShaderCode.data();
			fragmentShaderInfo.code_size = fragShaderCode.size();
			fragmentShaderInfo.num_samplers = 0;
			};
		if (!gpuDevice->CreateShaders()) {
			return false;
		}
		GPUVertexBufferDescription vertexBufferDesc[1]{};
		vertexBufferDesc[0].slot = 0;
		vertexBufferDesc[0].input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
		vertexBufferDesc[0].instance_step_rate = 0;
		vertexBufferDesc[0].pitch = sizeof(Vertex);
		SDL_GPUVertexAttribute vertexAttributes[2]{};
		vertexAttributes[0].location = 0;
		vertexAttributes[0].buffer_slot = 0;
		vertexAttributes[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
		vertexAttributes[0].offset = offsetof(Vertex, pos);
		vertexAttributes[1].location = 1;
		vertexAttributes[1].buffer_slot = 0;
		vertexAttributes[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3;
		vertexAttributes[1].offset = offsetof(Vertex, color);
		gpuDevice->onSetGraphicsPipelineCreateInfo = [&vertexBufferDesc, &vertexAttributes](GPUGraphicsPipelineCreateInfo& pipelineInfo) {
			pipelineInfo.vertex_input_state.vertex_attributes = vertexAttributes;
			pipelineInfo.vertex_input_state.num_vertex_attributes = 2;
			pipelineInfo.vertex_input_state.vertex_buffer_descriptions = vertexBufferDesc;
			pipelineInfo.vertex_input_state.num_vertex_buffers = 1;
			};
		if (!gpuDevice->CreateGraphicsPipeline()) {
			return false;
		}
		return true;
	}

	void UploadVertexData() {
		Vertex vertices[] = {
			// left bottom
			Vertex{ { -0.5f, -0.5f }, { 1.0f, 0.0f, 0.0f } },
			// right bottom
			Vertex{ {  0.5f, -0.5f }, { 0.0f, 1.0f, 0.0f } },
			// right top
			Vertex{ {  0.5f,  0.5f }, { 0.0f, 0.0f, 1.0f } },
			// left top
			Vertex{ { -0.5f,  0.5f }, { 1.0f, 1.0f, 0.0f } },
		};

		GPUTransferBuffer transferBuffer{ *gpuDevice, SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, sizeof(vertices) };
		if (!transferBuffer) {
			printf("Create transfer buffer failed! %s\n", SDL_GetError());
			return;
		}
		memcpy(GPUTransferBufferMapper{ transferBuffer }, vertices, sizeof(vertices));

		vertexBuffer = std::make_unique<GPUBuffer>(*gpuDevice, SDL_GPU_BUFFERUSAGE_VERTEX, (Uint32)sizeof(vertices));
		if (!*vertexBuffer) {
			printf("Create vertex buffer failed! %s\n", SDL_GetError());
			return;
		}

		GPUCommandBuffer cmd{ *gpuDevice };
		if (!cmd) {
			return;
		}
		GPUTransferBufferLocation srcLocation{};
		srcLocation.transfer_buffer = transferBuffer;
		srcLocation.offset = 0;
		GPUBufferRegion dstRegion{};
		dstRegion.buffer = *vertexBuffer;
		dstRegion.offset = 0;
		dstRegion.size = sizeof(vertices);
		GPUCopyPass	copyPass{ cmd };
		copyPass.UploadToGPUBuffer(srcLocation, dstRegion);
	}

	void UploadIndicesData() {
		Uint32 indices[] = {
			0, 1, 2,
			0, 3, 2,
		};

		GPUTransferBuffer transferBuffer{ *gpuDevice, SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, sizeof(indices) };
		if (!transferBuffer) {
			printf("Create transfer buffer failed! %s\n", SDL_GetError());
			return;
		}
		memcpy(GPUTransferBufferMapper{ transferBuffer }, indices, sizeof(indices));

		indexBuffer = std::make_unique<GPUBuffer>(*gpuDevice, SDL_GPU_BUFFERUSAGE_INDEX, (Uint32)sizeof(indices));
		if (!*indexBuffer) {
			printf("Create index buffer failed! %s\n", SDL_GetError());
			return;
		}

		GPUCommandBuffer cmd{ *gpuDevice };
		if (!cmd) {
			return;
		}
		GPUTransferBufferLocation srcLocation{};
		srcLocation.transfer_buffer = transferBuffer;
		srcLocation.offset = 0;
		GPUBufferRegion dstRegion{};
		dstRegion.buffer = *indexBuffer;
		dstRegion.offset = 0;
		dstRegion.size = sizeof(indices);
		GPUCopyPass	copyPass{ cmd };
		copyPass.UploadToGPUBuffer(srcLocation, dstRegion);
	}

	void Present() const {
		if (window->GetWindowFlags() & SDL_WINDOW_MINIMIZED) {
			return;
		}
		GPUCommandBuffer cmd{ *gpuDevice };
		if (!cmd) {
			return;
		}
		GPUSwapchainTexture swapchainTexture{ cmd, *window };
		if (!swapchainTexture) {
			return;
		}
		GPUColorTargetInfo colorTargetInfo{};
		colorTargetInfo.texture = swapchainTexture;
		colorTargetInfo.clear_color = { 0.2f, 0.3f, 0.3f, 1.0f };
		colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR;
		colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE;
		colorTargetInfo.mip_level = 0;
		colorTargetInfo.layer_or_depth_plane = 0;
		colorTargetInfo.cycle = true;
		colorTargetInfo.cycle_resolve_texture = false;
		GPURenderPass renderPass{ cmd, &colorTargetInfo, 1 };
		if (!renderPass) {
			return;
		}

		renderPass.BindGraphicsPipeline(*gpuDevice->pipeline);
		renderPass.BindVertexBuffers(0, { *vertexBuffer, 0 });
		renderPass.BindIndexBuffer({ *indexBuffer, 0 }, SDL_GPU_INDEXELEMENTSIZE_32BIT);

		GPUViewport viewport{};
		viewport.x = 0.0f;
		viewport.y = 0.0f;
		viewport.w = (float)swapchainTexture.width;
		viewport.h = (float)swapchainTexture.height;
		viewport.min_depth = 0.0f;
		viewport.max_depth = 1.0f;
		renderPass.SetViewport(viewport);
		renderPass.DrawIndexedPrimitives(6, 1, 0, 0, 0);
	}

	bool Init() {
		auto& sdlfx = SDLFramework::GetInstance();
		sdlfx.SetFPS(200);
		window = std::make_unique<SDLWindow>(_T("testSDLGPU"), 800, 600, 0);
		if (!*window) {
			return false;
		}
		sdlfx.onFPS = [this, &sdlfx](Uint32 fps) {
			window->SetTitle(std::format(_T("fps:{}, delta:{}"), fps, sdlfx.deltaTime));
			};
		if (!InitGPUDevice()) {
			return false;
		}

		UploadVertexData();
		UploadIndicesData();
		if (!*vertexBuffer || !*indexBuffer) {
			return false;
		}

		return true;
	}

	void Run() {
		auto& sdlfx = SDLFramework::GetInstance();
		bool runing = true;
		while (runing) {
			sdlfx.CountFrame();
			while (sdlfx.PollEvent()) {
				if (sdlfx.event.type == SDL_EVENT_QUIT) {
					runing = false;
					break;
				}
			}
			Present();
		}
	}

	void Cleanup() {

	}
};

int main() {
	try {
		AppContext app;
		if (app.Init()) {
			app.Run();
		}
		app.Cleanup();
	}
	catch (const std::exception& e) {
		printf("App error! '%s'\n", e.what());
		return -1;
	}
	return 0;
}